package ru.isys.xml.util.builder;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import ru.isys.xml.XmlItem;

public class ModelBuilder
{
    private static ModelBuilder instance = new ModelBuilder();
    DocumentBuilderFactory docFactory;
    TransformerFactory transformerFactory;
    DocumentBuilder docBuilder;
    Transformer transformer;
    Document doc;
    List<Element> elements;
    List<XmlItem> xmlItems;
    String tmpfile = "tmpModel.xml";
    String xmlModel;

    private ModelBuilder()
    {
        this.docFactory = DocumentBuilderFactory.newInstance();
        try
        {
            this.docBuilder = docFactory.newDocumentBuilder();
            this.doc = docBuilder.newDocument();
            this.elements = new ArrayList<Element>();
            this.xmlItems = new ArrayList<XmlItem>();
            this.transformerFactory = TransformerFactory.newInstance();            
            this.transformer = transformerFactory.newTransformer();
        }
        catch (ParserConfigurationException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (TransformerConfigurationException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // this.transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
    }

    public static ModelBuilder getInstance() throws ParserConfigurationException, TransformerConfigurationException
    {
        if (instance == null)
        {
            return new ModelBuilder();
        }
        else
        {
            return instance;
        }
    }

    public Element createNode(String namespaceURI, String prefix, String localpart)
    {
        String qualifiedName = this.getQualifiedName(prefix, localpart);
        return doc.createElementNS(namespaceURI, qualifiedName);
    }

    private String getQualifiedName(String prefix, String localpart)
    {
        return prefix + ":" + localpart;
    }

    public XmlItem setFullNameXmlItem(QName toSet, QName parent)
    {

        String itemName = this.getQualifiedName(toSet);
        XmlItem xmlItem = this.getXmlItemByName(itemName);
        if (parent != null)
        {
            XmlItem parentItem = this.getXmlItemByName(this.getQualifiedName(parent));
            xmlItem.setFullName(parentItem.getFullName() + "/" + itemName);
            xmlItem.setName(this.getQualifiedName(toSet));
            System.out.println(xmlItem.getFullName());
        }
        else
        {
            xmlItem.setFullName(itemName);
            xmlItem.setName(getQualifiedName(toSet));
            System.out.println(xmlItem.getFullName());           
        }
        System.out.println(xmlItem.getFullName());
        return xmlItem;
    }

    private String getQualifiedName(QName name)
    {
        return name.getPrefix() + ":" + name.getLocalPart();
    }

    public XmlItem createXmlItem(QName toCreate, Stack<QName> stack)
    {
        XmlItem xmlItem = new XmlItem();        
        xmlItem.setName(getQualifiedName(toCreate));
        if (!stack.empty())
        {
            xmlItem.setFullName(this.getXmlItemByName(this.getQualifiedName(stack.peek())).getFullName() + "/" + this.getQualifiedName(toCreate));
        }
        else
        {
            xmlItem.setFullName(this.getQualifiedName(toCreate));
        }
        xmlItems.add(xmlItem);
        System.out.println(xmlItem.getName());
        return xmlItem;
    }

    public void appendChildToDocument(Element child)
    {
        doc.appendChild(child);
    }

    public Document getDoc()
    {
        return doc;
    }

    public void appendChild(Element parent, Element child)
    {
        parent.appendChild(child);
    }

    public String transformAndShow(OutputStream outputStream) throws TransformerException, IOException, DocumentException
    {
        StringWriter sw = new StringWriter();
        PrintWriter fw = new PrintWriter(new FileWriter(this.tmpfile));
        BufferedReader bf = new BufferedReader(new FileReader(this.tmpfile));

        DOMSource source = new DOMSource(doc);
        StreamResult fileResult;
        fileResult = new StreamResult(new FileOutputStream(this.tmpfile));
        transformer.transform(source, fileResult);
        String xml = "";
        String line = bf.readLine();
        while (line != null)
        {
            xml += line;
            line = bf.readLine();
        }

        org.dom4j.Document doc = org.dom4j.DocumentHelper.parseText(xml);
        OutputFormat format = OutputFormat.createPrettyPrint();
        XMLWriter xw = new XMLWriter(sw, format);
        xw.write(doc);
        String result = sw.toString();
        this.xmlModel = result;
        System.out.println(result);
        fw.print(result);
        fw.flush();
        return result;
    }

    public String transform(OutputStream outputStream) throws TransformerException
    {
        if (outputStream != null)
        {
            return doc.toString();
        }
        else
        {
            try
            {
                DOMSource source = new DOMSource(doc);
                StreamResult systemResult = new StreamResult(System.out);
                StreamResult fileResult;
                fileResult = new StreamResult(new FileOutputStream(this.tmpfile));
                transformer.transform(source, systemResult);
                transformer.transform(source, fileResult);
            }
            catch (FileNotFoundException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return doc.toString();
        }
    }

    public void addElement(Element element)
    {
        this.elements.add(element);
    }

    public Element getElementByName(String tag)
    {
        for (Element element : elements)
        {
            if (element.getNodeName().equals(tag))
            {
                return element;
            }

        }
        return null;
    }

    public XmlItem getXmlItemByName(String name)
    {
        for (XmlItem xmlItem : xmlItems)
        {
            if (xmlItem.getName().equals(name))
            {
                return xmlItem;
            }
        }
        return null;
    }

    public void addAttributeToXmlItem(XmlItem item, String attributeName, String attributeValue)
    {
        XmlItem tmpItem = getXmlItemByName(item.getName());
        tmpItem.addAttribute(attributeName, attributeName);
    }

    public List<XmlItem> getXmlItems()
    {
        return this.xmlItems;
    }

    public String getXmlModel()
    {
        return xmlModel;
    }

    public void setXmlModel(String xmlModel)
    {
        this.xmlModel = xmlModel;
    }

    public void addXmlItem(XmlItem itemToAdd)
    {
        this.xmlItems.add(itemToAdd);
    }
}
